package p.sgssxln.javafx.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.JarClassLoader;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.NamingCase;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.ds.simple.SimpleDataSource;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.setting.GroupedMap;
import cn.hutool.setting.Setting;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputDialog;
import javafx.scene.layout.Pane;
import lombok.Data;
import lombok.experimental.Accessors;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.parser.CCJSqlParser;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.ClasspathResourceLoader;
import p.sgssxln.Main;
import p.sgssxln.javafx.bean.ClassInfo;
import p.sgssxln.javafx.bean.FiledInfo;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;

public class SqlController {
    protected static final Log log = Log.get();

    public static GroupTemplate gt = null;

    static {
        ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("template/def");
        Configuration cfg = null;
        try {
            cfg = new Configuration();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        gt = new GroupTemplate(resourceLoader, cfg);
    }

    public static LinkedHashMap<String, StringBuilder> classMap = new LinkedHashMap<>();


    static Setting setting;
    @FXML // ResourceBundle that was given to the FXMLLoader
    private ResourceBundle resources;

    @FXML // URL location of the FXML file that was given to the FXMLLoader
    private URL location;

    @FXML // fx:id="index"
    private Pane index; // Value injected by FXMLLoader


    @FXML
    TextField sql_user;
    @FXML
    TextField sql_url;
    @FXML
    TextField sql_pwd;
    @FXML
    TextField sql_drive;


    @FXML
    private TextField package_name;

    @FXML
    ListView<Label> sql_config_list;


    @FXML
        // This method is called by the FXMLLoader when initialization is complete
    void initialize() {
        File file = new File(Main.basePath, "config" + File.separator + "db.setting");
        if (file.exists() == false) {
            try {
                file.getParentFile().mkdirs();
                if (file.createNewFile()) {

                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        setting = new Setting(file.getPath());
        List<String> groups = setting.getGroups();
        if (CollUtil.isNotEmpty(groups)) {
            initText();
            sql_config_list.getItems().clear();
            sql_config_list.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
                try {
                    if (newValue == null) {
                        return;
                    }
                    initText();
                    String byGroup = setting.getByGroup("json", newValue.getText());
                    SqlConfig dbConfig = JSONUtil.toBean(byGroup, SqlConfig.class);
                    sql_drive.setText(dbConfig.getDriver());
                    sql_pwd.setText(dbConfig.getPass());
                    sql_user.setText(dbConfig.getUser());
                    sql_url.setText(dbConfig.getUrl());
                } catch (Exception e) {
                    log.error(e);
                }
            });
            for (String group : groups) {
                Label label = new Label(group);
                label.setUserData(group);
                ObservableList<Label> items = sql_config_list.getItems();
                items.add(label);
            }
        } else {
            sql_config_list.getItems().clear();
        }
    }

    private SqlConfig sqlInfo() {
        initText();
        SqlConfig dbConfig = null;
        try {
            if (sql_url == null) {
                return null;
            }
            dbConfig = new SqlConfig(sql_drive.getText(), sql_url.getText(), sql_user.getText(), sql_pwd.getText());
        } catch (Exception e) {
            log.error("db配置错误", e);
        }
        return dbConfig;
    }

    private void initText() {
        // sql_user = (TextField) index.lookup("#sql_user");
        // sql_url = (TextField) index.lookup("#sql_url");
        // sql_pwd = (TextField) index.lookup("#sql_pwd");
        // sql_drive = (TextField) index.lookup("#sql_drive");
    }


    @FXML
    public void reset() {

    }

    public void save() {
        SqlConfig dbConfig = sqlInfo();

        TextInputDialog textInputDialog = new TextInputDialog();
        textInputDialog.setTitle("保存配置");
        textInputDialog.setHeaderText("请输入名称");
        Label selectedItem = sql_config_list.getSelectionModel().getSelectedItem();
        if (selectedItem != null) {
            textInputDialog.getEditor().setText(selectedItem.getText());
        }
        textInputDialog.showAndWait().ifPresent(s -> {
            if (StrUtil.isBlank(s)) {
                Alert alert = new Alert(Alert.AlertType.ERROR);
                alert.setTitle("不允许空");
                alert.setContentText("请重新输入保存名称");
                alert.show();
                return;
            }
            GroupedMap groupedMap = setting.getGroupedMap();
            groupedMap.put(s, "json", JSONUtil.toJsonStr(dbConfig));
            setting.store();
            initialize();
        });
    }


    public void test() {
        try {
            Db db = getConn();
            Connection connection = db.getConnection();
            if (connection != null) {
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setContentText("连接成功");
                db.closeConnection(connection);
                alert.show();
            }
        } catch (Exception e) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setContentText("连接失败:" + e.getMessage());
            alert.showAndWait();
        }
    }


    public StringBuilder xmlStr;

    public void 初始化生成相关() {
        xmlStr = new StringBuilder();
    }

    /**
     * 生成类信息
     *
     * @param map          地图
     * @param className    类名
     * @param indentation  缩进
     * @param columnPrefix sql原始别名
     */
    public void generateClassInfo(LinkedHashMap<String, Object> map, String className, int indentation, String columnPrefix) {
        // 是否选择了下划线转驼峰
        boolean istoCame = ((CheckBox) index.lookup("#to_came")).isSelected();
        if (istoCame) {
            className = NamingCase.toCamelCase(className);
        }
        if (classMap.containsKey(className)) {
            return;
        }

        boolean 初始化 = xmlStr == null || xmlStr.isEmpty();//如果是初始化的
        String packageNameText = package_name.getText();
        if (StrUtil.isBlank(packageNameText)) {
            packageNameText = "def";
        }
        if (初始化) {
            xmlStr = new StringBuilder();
            xmlAppend(1, "<resultMap type=\"{}\" id=\"{}\">", packageNameText + "." + className, className + "Map");
        }
        ClassInfo classInfo = new ClassInfo().setClassName(className).setTableRemark("").setPackageName(packageNameText).setSqlPath(className);
        LinkedList<FiledInfo> filedList = new LinkedList<>();
        classInfo.setFiledList(filedList);
        indentation += 1;
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Object value = entry.getValue();
            String key = entry.getKey();
            if (istoCame) {
                key = NamingCase.toCamelCase(key);
            }
            if (value instanceof String str) {
                FiledInfo e = new FiledInfo().setFieldName(key).setSqlName(entry.getKey()).setFieldType(str).setCommentary("");
                filedList.add(e);
                // <result property="checkFailReason" column="check_fail_reason"/>
                xmlAppend(indentation, "<result property=\"{}\" column=\"{}\"/>", e.getFieldName(), e.getSqlName());

            } else {
                String cName = CharSequenceUtil.upperFirstAndAddPre(key, className);
                FiledInfo e = new FiledInfo().setFieldName(key).setSqlName(entry.getKey()).setFieldType(cName).setCommentary("");
                filedList.add(e);
                xmlAppend(indentation, "<association property=\"{}\" columnPrefix=\"{}\">", e.getFieldName(), columnPrefix + entry.getKey() + ".");
                generateClassInfo((LinkedHashMap<String, Object>) value, cName, indentation, columnPrefix + entry.getKey() + ".");
                xmlAppend(indentation, "</association>");
            }
        }


        Template template = gt.getTemplate("java.beetl");
        template.binding(JSONUtil.parseObj(classInfo));

        String render = template.render();
        File javaPath = new File(Main.basePath, "generate" + File.separator + "java" + File.separator + packageNameText);
        javaPath.mkdirs();
        File file = new File(javaPath, className + ".java");

        if (初始化) {
            xmlAppend(1, "</resultMap>");
            File xml = new File(javaPath, className + ".xml");
            FileUtil.writeUtf8String(xmlStr.toString(), xml);
            RuntimeUtil.exec("explorer", javaPath.getAbsolutePath());
        }

        FileUtil.writeUtf8String(render, file);
    }


    public Db getConn() {
        SqlConfig sqlConfig = sqlInfo();
        Class<java.sql.Driver> aClass = null;
        //region 本来是想之后做成只要把数据库驱动放到lib目录自动加载的
        try {
            aClass = (Class<Driver>) ClassLoaderUtil.loadClass(sqlConfig.getDriver());
        } catch (Exception e) {
            try {
                JarClassLoader lib = JarClassLoader.load(new File(Main.basePath, "lib"));
                Class.forName(sqlConfig.getDriver(), true, lib);
            } catch (Exception ex) {
                log.error(ex);
            }
        }
        //endregion
        try {
            DataSource ds = new SimpleDataSource(sqlConfig.getUrl(), sqlConfig.getUser(), sqlConfig.getPass(), sqlConfig.getDriver());
            ds.setLoginTimeout(10);
            return Db.use(ds);
        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException(e);
        }

    }


    @FXML
    public void execQuery() {
        Db conn = getConn();

        try {
            conn.setCaseInsensitive(false);
            TextArea lookup = (TextArea) index.lookup("#sql_query");
            String text = lookup.getText();
            if (StrUtil.isNotBlank(text)) {
                Entity entity = conn.queryOne(text);
                Set<Map.Entry<String, Object>> entries = entity.entrySet();
                LinkedHashMap<String, Object> 所有字段 = new LinkedHashMap<>();
                for (Map.Entry<String, Object> entry : entries) {
                    String key = entry.getKey();
                    Object value = entry.getValue();
                    if (key.contains(".")) {
                        //则是至少二级目标
                        String kt = key;
                        LinkedHashMap tmap = 所有字段;
                        do {
                            String 前缀 = kt.substring(0, kt.indexOf("."));
                            LinkedHashMap orDefault = (LinkedHashMap) tmap.getOrDefault(前缀, new LinkedHashMap<>());
                            tmap.put(前缀, orDefault);
                            tmap = orDefault;
                            kt = kt.substring(kt.indexOf(".") + 1);
                        } while (kt.contains("."));
                        {
                            String k = kt;
                            if (value == null) {
                                tmap.put(k, Object.class.getName());
                            } else {
                                tmap.put(k, value.getClass().getName());
                            }

                        }
                    } else {
                        if (value == null) {
                            所有字段.put(key, Object.class.getName());
                        } else {
                            所有字段.put(key, value.getClass().getName());
                        }
                    }

                }
                初始化生成相关();
                generateClassInfo(所有字段, "SqlBean", 1, "");
            }

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }


    public void del() {
        Label selectedItem = sql_config_list.getSelectionModel().getSelectedItem();
        if (selectedItem != null) {
            GroupedMap groupedMap = setting.getGroupedMap();
            groupedMap.remove(selectedItem.getText());
            setting.store();
            initialize();
        } else {
            new Alert(Alert.AlertType.WARNING, "请选择要删除的条目").show();
        }
    }


    public void sqlAsName() {
        TextArea sql_as_input = (TextArea) index.lookup("#sql_as_input");
        TextArea sql_as_out = (TextArea) index.lookup("#sql_as_out");
        String text = sql_as_input.getText();
        CCJSqlParser ccjSqlParser = CCJSqlParserUtil.newParser(text);
        try {
            PlainSelect selectBody = (PlainSelect) ccjSqlParser.SelectBody();
            List<SelectItem> selectItems = selectBody.getSelectItems();
            selectItems.forEach(selectItem -> {
                if (selectItem instanceof SelectExpressionItem it) {
                    String str = it.getExpression().toString();
                    StringBuilder st = new StringBuilder();
                    // String s = st.append(str).append(" as ").append("'").append(str.replace("`", "").replaceAll("\\s", "")).append("'").toString();
                    if (it.getAlias() == null) {
                        try {
                            it.setAlias(new Alias(st.append("'").append(str.replace("`", "").replaceAll("\\s", "")).append("'").toString(), true));
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            });
            sql_as_out.setText(selectBody.toString());
        } catch (Throwable e) {
            log.error(e);
        }
    }


    /**
     * 缩进
     *
     * @param 缩进 缩进
     * @param t  t
     * @return {@link String}
     */
    private String indentation(int 缩进, String t) {
        StringBuilder f = new StringBuilder();
        while (缩进-- > 0) {
            f.append(t);
        }
        return f.toString();
    }

    /**
     * xml添加
     *
     * @param indentation 缩进
     * @param format      格式
     * @param str         str
     * @return {@link StringBuilder}
     */
    private StringBuilder xmlAppend(int indentation, String format, Object... str) {
        xmlStr.append(indentation(indentation, "    "));
        xmlStr.append(StrUtil.format(format, str)).append("\r\n");

        return xmlStr;
    }
}


@Data
@Accessors(chain = true)
class SqlConfig {
    private String driver;        //数据库驱动
    private String url;            //jdbc url
    private String user;            //用户名
    private String pass;            //密码

    public SqlConfig(String driver, String url, String user, String pass) {
        this.driver = driver;
        this.url = url;
        this.user = user;
        this.pass = pass;
    }

    public SqlConfig(String url, String user, String pass) {
        this.url = url;
        this.user = user;
        this.pass = pass;
    }


}
